package ru.spbau.bytecode.graph;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;


public class Graph {
	private final List<CompositeVertex> vertices = new LinkedList<CompositeVertex>();
	private final Set<Edge> edges = new HashSet<Edge>();
	
	private static final int OFFSET_X = 20;
	private static final int OFFSET_Y = 20;
	private static final int COMPOSITE_COLUMNS = 3;
	private static final int SIMPLE_COLUMNS = 2;
	
	
	public void addVertex(CompositeVertex v) {
		if (!vertices.contains(v)) {
			vertices.add(v);
		}
	}
	
	public void addEdge(Edge e) {
		if (!edges.contains(e)) {
			Vertex v = findVertex(e.getSourceVertex());
			Vertex u = findVertex(e.getTargetVertex());
			
			if (v == null || u == null) {
				throw new IllegalStateException("Vertices for this edge not found");
			}
			
			edges.add(new Edge(v, u));
		}
	}
	
	private Vertex findVertex(Vertex v) {
		Vertex res = null;
		if (vertices.contains(v)) {
			for (Vertex u: vertices) {
				if (v.equals(u)) {
					res = u;
				}
			}
		} else {
			for (CompositeVertex u: vertices) {
				for (Vertex w: u.getInnerVertices()) {
					if (w.equals(v)) {
						res = w;
					}
				}
			}
		}
		
		return res;
	}
	
	public void catchVertexAtPoint(Point p) {
		Vertex innerVertex = null;
		for (CompositeVertex v : vertices) {
			for (Vertex u : v.getInnerVertices()) {
				if (u.pointInside(p)) {
					innerVertex = u;
				}
			}
		}
		
		CompositeVertex compositeVertex = null;
		for (CompositeVertex v : vertices) {
			if (v.pointInside(p)) {
				compositeVertex = v;
			}
		}
		
		if (innerVertex == null) {
			if (compositeVertex == null) {
				return;
			}
			compositeVertex.setCatched(true);
			vertices.remove(compositeVertex);
			vertices.add(compositeVertex);
		}
		
		if (compositeVertex.containsInnerVertex(innerVertex)) {
			compositeVertex.moveVertexToTop(innerVertex);
			innerVertex.setCatched(true);
		} else {
			compositeVertex.setCatched(true);
			vertices.remove(compositeVertex);
			vertices.add(compositeVertex);
		}
	}
	
	public void freeCatchedVertices() {
		for (Vertex v: vertices) {
			v.setCatched(false);
		}
	}
	
	// TODO make test
	public void moveCatchedVertices(int dx, int dy) {
		for (CompositeVertex v : vertices) {
			if (v.isCatched()) {
				v.move(dx, dy);
			} else {
				for (Vertex u: v.getInnerVertices()) {
					if (u.isCatched()) {
						u.move(dx, dy);
					}
				}
			}
		}
	}
	
	public List<CompositeVertex> getVertices() {
		return Collections.unmodifiableList(vertices);
	}
	
	public Set<Edge> getEdges() {
		return Collections.unmodifiableSet(edges);
	}
	
	public Dimension calculateGraphSize() {
		int maxX = 0;
		int maxY = 0;
		for (CompositeVertex v : vertices) {
			maxX = Math.max(maxX, v.getLocation().x + v.getSize().width);
			maxY = Math.max(maxY, v.getLocation().y + v.getSize().height);
		}

		Dimension size = new Dimension(maxX, maxY);
		return size;
	}

	public void draw(Graphics g) {
		
		for (CompositeVertex v : vertices) {
			v.draw(g);
		}
		
		for (Edge e : edges) {
			e.draw(g);
		}
	}
	
	public void placeVertices(Point position) {
		int col = 0;
		
		int maxX = 0;
		int maxY = 0;
		
		int curX = OFFSET_X;
		int curY = OFFSET_Y;
		
		int dy = 0;
		
		for (CompositeVertex v: vertices) {
			Point p = new Point(curX + position.x, curY + position.y);
			Dimension dim = placeSimpleVertices(p, v.getInnerVertices());
			
			if (dim.width < v.getSize().width) {
				dim.width = v.getSize().width;
			}
			if (dim.height < v.getSize().height) {
				dim.height = v.getSize().height;
			}
			v.setLocation(p);
			v.setSize(dim);
			maxX = Math.max(curX + dim.width + OFFSET_X, maxX);
			maxY = Math.max(curY + dim.height + OFFSET_Y, maxY);
			
			dy = Math.max(dy, dim.height);
			
			if (col < COMPOSITE_COLUMNS) {
				curX += dim.width + OFFSET_X;
				col++;
			} else {
				curX = OFFSET_X;
				curY += dy + OFFSET_Y;
				col = 0;
				dy = 0;
			}

			for (Vertex u: v.getInnerVertices()) {
				u.setBounds(new Rectangle(p.x, p.y, dim.width, dim.height));
			}
		}
	}
	
	private Dimension placeSimpleVertices(Point position,
			List<Vertex> vertices) {
		int col = 0;
		
		int maxX = OFFSET_X;
		int maxY = OFFSET_Y;
		
		int curX = OFFSET_X;
		int curY = OFFSET_Y;
		
		for (Vertex v: vertices) {
			Point p = new Point(curX + position.x, curY + position.y);
			Dimension dim = v.getSize();
			v.setLocation(p);
			maxX = Math.max(curX + dim.width + OFFSET_X, maxX);
			maxY = Math.max(curY + dim.height + OFFSET_Y, maxY);
			
			if (col < SIMPLE_COLUMNS) {
				curX += dim.width + OFFSET_X;
				col++;
			} else {
				curX = OFFSET_X;
				curY += dim.height + OFFSET_Y;
				col = 0;
			}
		}
		return new Dimension(maxX, maxY);
	}
}
